#include <stdlib.h>
#include <string.h>

#include "fs.h"
#include "metafs.h"
#include "isofs.h"

#include "../log.h"

MetaFileSystem mfs;

void mfs_init()
{
    mfs.numfs = 0;
    mfs.current = 0;
    mfs.currentDirectory = malloc(2);
    mfs.currentDirectory[0] = '\0';
    strcpy(mfs.currentDirectory, "/");
    mfs.fileSystems = NULL;
}

void mfs_free()
{
    u32 i;
    for (i = 0; i < mfs.numfs; i++)
        mfs_umount(mfs.fileSystems[i].prefix);
}

System *mfs_getHandleOwner(u32 handle)
{
    u32 i;

    for (i = 0; i < mfs.numfs; i++)
        if (fs_ownsHandle(&mfs.fileSystems[i], handle))
            return &mfs.fileSystems[i];

    return 0;
}

s32 mfs_mapFilePath(const char *inpath, System *system)
{
    u32 i;
    char *subpath;
    u8 hasdrv = 0;
    for (i = 0; i < strlen(inpath) && inpath[i] != '/'; i++)
        if (inpath[i] == ':')
            hasdrv = 1;

    /* absolute path with drive */
    if (hasdrv)
    {
        for (i = 0; i < mfs.numfs; i++)
        {
            s32 prefLen = strlen(mfs.fileSystems[i].prefix);
            subpath = malloc(prefLen + 1);
            strncpy(subpath, inpath, prefLen);
            subpath[prefLen] = '\0';
            if (strcmp(mfs.fileSystems[i].prefix, subpath) == 0)
            {
                system->system = mfs.fileSystems[i].system;
                system->type = mfs.fileSystems[i].type;
                free(subpath);
                return 1;
            }
            free(subpath);
        }
    }
    /* relative path */
    else
    {
        char *newpath = malloc(strlen(mfs.currentDirectory) + strlen(inpath) + 1);
        newpath[0] = 0;
        strcpy(newpath, mfs.currentDirectory);
        strcat(newpath, inpath);
        if (mfs_mapFilePath(newpath, system)) {
            free(newpath);
            return 1;
        }
        else {
            free(newpath);
            return 0;
        }
    }

    return 0;
}

void mfs_mount(const char *prefix, FSType type, System *system)
{
    System x;
    u32 i;
    for (i = 0; i < mfs.numfs; i++)
    {
        if (strcmp(mfs.fileSystems[i].prefix, prefix) == 0)
        {
            _log(WRN, FS, "Filesystem with prefix %s already existing, replacing", prefix);
            mfs.fileSystems[i].system = system->system;
            mfs.fileSystems[i].type = type;
            return;
        }
    }

    if (mfs.fileSystems == NULL)
        mfs.fileSystems = malloc(sizeof(System));
    else
        mfs.fileSystems = realloc(mfs.fileSystems, (mfs.numfs + 1) * sizeof(System));

    x.prefix = malloc(sizeof(char) * (strlen(prefix) + 1));
    x.prefix[0] = '\0';
    strcpy(x.prefix, prefix);
    x.system = system->system;
    x.type = type;
    mfs.fileSystems[mfs.numfs++] = x;
}

void mfs_umount(const char *prefix)
{
    u32 i;

    for (i = 0; i < mfs.numfs; i++)
        if (strcmp(mfs.fileSystems[i].prefix, prefix) == 0)
            break;

    fs_free(&mfs.fileSystems[i]);

    if (i == mfs.numfs) {
        _log(ERR, FS, "Filesystem %s can't be removed: it has not been found!", prefix);
        return;
    }

    free(mfs.fileSystems[i].prefix);

    mfs.numfs--;
    for (; i < mfs.numfs; i++)
        mfs.fileSystems[i] = mfs.fileSystems[i + 1];

    if (mfs.numfs == 0) {
        free(mfs.fileSystems); /* free the last filesystem */
        mfs.fileSystems = NULL;
    }
    else
        mfs.fileSystems = realloc(mfs.fileSystems, mfs.numfs * sizeof(System));
}

char *mfs_getRealPath(const char *inpath)
{
    System system;
    if (mfs_mapFilePath(inpath, &system))
        return fs_getRealPath(&system, inpath);
    else
        return NULL;
}

s32 mfs_openFile(const char *ofilename, FileAccess access)
{
    System system;
    if (mfs_mapFilePath(ofilename, &system))
        return fs_openFile(&system, ofilename, access);
    return 0;
}

FileInfo mfs_getFileInfo(const char *filename)
{
    System system;
    FileInfo fi;
    if (mfs_mapFilePath(filename, &system))
        fi = fs_getFileInfo(&system, filename);
    return fi;
}

void mfs_closeFile(u32 handle)
{
    System *sys = mfs_getHandleOwner(handle);
    if (sys)
        fs_closeFile(sys, handle);
}

u32 mfs_readFile(u32 handle, char *pointer, u32 size)
{
    System *sys = mfs_getHandleOwner(handle);
    if (sys)
        return fs_readFile(sys, handle, pointer, size);
    return 0;
}

u32 mfs_readFileToAddr(u32 handle, u32 addr, u32 size)
{
    System *sys = mfs_getHandleOwner(handle);
    if (sys)
        return fs_readFileToAddr(sys, handle, addr, size);
    return 0;
}

s32 mfs_seekFile(u32 handle, s32 position, FileMove type)
{
    System *sys = mfs_getHandleOwner(handle);
    if (sys)
        return fs_seekFile(sys, handle, position, type);
    return 0;
}

s32 mfs_writeFile(u32 handle, u8 *pointer, u32 size)
{
    System *sys = mfs_getHandleOwner(handle);
    if (sys)
        return fs_writeFile(sys, handle, pointer, size);
    return 0;
}

s32 mfs_writeFileFromAddr(u32 handle, u32 addr, u32 size)
{
    System *sys = mfs_getHandleOwner(handle);
    if (sys)
        return fs_writeFileFromAddr(sys, handle, addr, size);
    return 0;
}

s8 mfs_mkdir(const char *dir, s32 mode)
{
    System system;
    if (mfs_mapFilePath(dir, &system))
        return fs_mkdir(&system, dir, mode);
    return -1;
}

u32 mfs_getNewHandle()
{
    return ++mfs.current;
}

u32 mfs_getHandle()
{
    return mfs.current;
}

void mfs_showFS()
{
    u32 i;
    _log(INF, FS, "FileSystems currently mounted:");
    for (i = 0; i < mfs.numfs; i++)
        _log(INF, FS, "- %s (%d)", mfs.fileSystems[i].prefix, mfs.fileSystems[i].type);
}

u32 mfs_getDirListing(const char *path, FileInfo **listing)
{
    System sys;
    if (mfs_mapFilePath(path, &sys))
        return fs_getDirListing(&sys, path, listing);
    else
        return 0;
}

void mfs_freeDirListing(const char *path, FileInfo *listing)
{
    System sys;
    if (mfs_mapFilePath(path, &sys))
        fs_freeDirListing(&sys, listing);
}

void mfs_chdir(const char *dir)
{
    u8 hasdrv = 0;
    u32 i;
    for (i = 0; i < strlen(dir) && dir[i] != '/'; i++)
        if (dir[i] == ':')
            hasdrv = 1;

    if (hasdrv)
    {
        if (dir[strlen(dir) - 1] != '/')
        {
            mfs.currentDirectory = realloc(mfs.currentDirectory, strlen(dir) + 2);
            strcpy(mfs.currentDirectory, dir);
            strcat(mfs.currentDirectory, "/");
        }
        else {
            mfs.currentDirectory = realloc(mfs.currentDirectory, strlen(dir) + 1);
            strcpy(mfs.currentDirectory, dir);
        }
    }
    else {
        mfs.currentDirectory = realloc(mfs.currentDirectory, strlen(dir) + strlen(mfs.currentDirectory) + 1);
        strcat(mfs.currentDirectory, dir);
    }
}

